package com.subpackage

import com.FileCrcMode
import com.entity.PackPath
import com.utils.ScriptUtils
import com.utils.XMLUtils
import com.utils.YamlUtil
import com.utils.ZipUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import org.apache.logging.log4j.kotlin.logger
import org.dom4j.Node
import org.dom4j.io.SAXReader
import java.nio.file.FileSystems
import java.nio.file.Path
import java.util.regex.Pattern
import kotlin.io.path.Path
import kotlin.io.path.name
import kotlin.system.exitProcess

/**
 * 打包基类
 */
open class BasePack {

    /**
     * 忽略文件
     */
    protected var ignoreList: Set<String>? = null

    /**
     * oss提交数据
     */
    lateinit var ossPush: MutableList<String>

    /**
     * 存储需要从OSS删除的文件路径集合
     *
     * 该集合包含了所有需要从远程存储中删除的文件路径，来源于[ContrastFile.deleteFileList]
     * 在打包完成后，这些文件路径将用于执行OSS删除操作
     */
    val deleteOssPush = mutableListOf<PackPath>()

    /**
     * 存储需要上传到OSS的新文件路径集合
     *
     * 该集合包含了所有新增和更新的文件路径，来源于[ContrastFile.addFileList]和[ContrastFile.updateFileList]
     * 在打包完成后，这些新增文件将被上传到远程存储中
     */
    val addOssPush = mutableListOf<PackPath>()


    /**
     * 保存文本 以及删除注释换行
     *
     * @param newStr
     * @return
     */
    fun scriptFormat(newStr: String): String {
        val result = StringBuilder()
        if (newStr.isNotEmpty()) {
            val m = Pattern.compile("(?m)^.*$").matcher(newStr)
            var s: String
            while (m.find()) {
                s = m.group()
                val index = s.indexOf("//")
                if (index != -1) {
                    val str = s.substring(0, index)
                    if (str.trim { it <= ' ' }.isEmpty()) {
                        s = str
                    }
                }
                if (s.trim { it <= ' ' }.isNotEmpty()) {
                    result.append(s + System.lineSeparator())
                }
            }
        }
        return result.toString()
    }

    fun saveString(file: PackPath, s: String) {
        file.writeText(s)
    }

    fun executeCompressJS(args: Array<String>) {
        // 构建命令行参数
        val command = mutableListOf(
            "java",
            "-cp",
            System.getProperty("java.class.path"),
            "com.subpackage.CompressJS"
        )
        command.addAll(args)
        logger.debug("execute $command")

        // 创建并启动进程
        val processBuilder = ProcessBuilder(command)
//        processBuilder.directory(File(System.getProperty("user.dir"))) // 设置工作目录
        processBuilder.inheritIO() // 继承输入输出流

        val process = processBuilder.start()
        val exitCode = process.waitFor()
        println("Process finished with exit code: $exitCode")
    }

    /**
     * 压缩JavaScript文件
     *
     * @param oldJs 需要压缩的JavaScript文件路径
     * @return 压缩后的文件路径
     * @throws IllegalArgumentException 当输入文件不是.js文件时抛出异常
     */
    fun compressJS(oldJs: PackPath): PackPath {
        // 验证输入文件扩展名
        if (oldJs.extension != "js") {
            throw IllegalArgumentException("Input file must be a .js file")
        }

        // 构造压缩后的文件路径
        val parent = oldJs.parent
        val fileName = oldJs.nameWithoutExtension
        val minJS = parent?.resolve("$fileName.min.js") ?: PackPath("$fileName.min.js")
        // 执行压缩操作
        compressJS(oldJs, minJS)
        return minJS
    }

    /**
     * 压缩JavaScript文件列表
     *
     * @param oldJs 需要压缩的JavaScript文件路径列表
     */
    fun compressJS(oldJs: List<PackPath>) {
        // 使用阻塞方式运行协程，等待所有压缩任务完成
        runBlocking {
            // 创建信号量控制并发数量，限制同时执行的压缩任务数为CPU核心数
            val semaphore = Semaphore(Runtime.getRuntime().availableProcessors())
            for (path in oldJs) {
                // 为每个文件启动一个协程进行压缩处理
                launch(Dispatchers.IO) {
                    // 通过信号量获取执行许可，控制并发执行
                    semaphore.withPermit {
                        logger.info("压缩JS文件 ${path.absolutePathString} ")
                        val newFileName = compressJS(path)
                        path.replacePath(newFileName)
                        logger.info("$newFileName 压缩完成 ")
                    }
                }
            }
        }
    }


    /**
     * 压缩js
     *
     * @param oldJs 未压缩的js
     * @param minJs 压缩后的js
     */
    fun compressJS(oldJs: PackPath, minJs: PackPath) {
        compressJS(oldJs.absolutePathString, minJs.absolutePathString)
    }

    fun compressJS(oldJs: String, minJs: String) {
        // println(oldJs)
        // println(minJs)

//        --warning_level (-W) [QUIET | DEFAULT | VERBOSE] 默认 DEFAULT
        //--logging_level SEVERE 默认是 WARNING
        val values = arrayOf(
            "--js", oldJs,
            "--js_output_file", minJs,
            "--logging_level", "SEVERE"
        )
        logger.info("压缩cmd: ${values.joinToString(" ")}")
        executeCompressJS(values)
    }

    /**
     * 删除xml注释 和空白行
     *
     * @param readFile 读取路径
     * @param outFile  输出路径
     */
    fun formatXml(readFile: PackPath, outFile: PackPath?) {
        val reader = SAXReader()
        val document = reader.read(readFile.inputStream())
        val root = document.rootElement
        // 删除注释
        val nodeIterator = root.nodeIterator()
        while (nodeIterator.hasNext()) {
            val node = nodeIterator.next()
            // 删除注释 和 空白文本
            if (node.nodeType == Node.COMMENT_NODE || node.nodeType == Node.TEXT_NODE) {
                nodeIterator.remove()
            } else if (node.nodeType == Node.ELEMENT_NODE) {
//                    String contentText = node.getText();
//                    // 将换行符替换成 转义符
//                    contentText = StringUtils.replaceAll(contentText, "\n", "&#x000A;");
//                    // 转换 &
//                    contentText = StringUtils.replaceAll(contentText, " & ", " &amp; ");
//                    node.setText(contentText);
            }
        }
        XMLUtils.saveXML(document, outFile ?: readFile)
    }


    /**
     * 执行一个文件
     * @param command 命令
     */
    fun exec(vararg command: String): Boolean {
        var commandString = command
        if (ScriptUtils.isWin()) {
            commandString = arrayOf("cmd", "/c", *commandString)
        }
        val processBuilder = ProcessBuilder(*commandString)
//        processBuilder.redirectOutput()
//		pb.directory(new File("cmd"))
        var runningStatus: Int
        var message: String?
        try {
            val process = processBuilder.start()

            val stdInput = process.inputReader()
            val stdError = process.errorReader()
            while ((stdInput.readLine().also { message = it }) != null) {
                if (message!!.length > 3 && (message.startsWith("%n%") || message.startsWith("%pro%"))) {
                    print("\r")
                    print(message)
                } else {
                    println("")
                    logger.info("[process-info] $message")
                }
            }
            while ((stdError.readLine().also { message = it }) != null) {
                logger.error("[process-err] $message")
            }
            runningStatus = process.waitFor()
            logger.info("[process-status] $runningStatus")
            if (runningStatus != 0) {
                return false
            }
        } catch (e: Exception) {
            logger.error("[process-err] " + commandString.joinToString(" "), e)
            return false
        }
        return true
    }


    /**
     * 授权
     *
     * @param tempFile 授权文件
     * @param u        授权码
     */
    @JvmOverloads
    fun authorization(tempFile: PackPath, u: String? = "755") {
        val builder = ProcessBuilder("/bin/chmod", u, tempFile.pathString)
        val process = builder.start()
        val rc = process.waitFor()
    }

    /**
     * 转换盘符为 反斜杠 (脚本还在用)
     * @param path
     */
    fun changeDiskCharacter(path: String?): String? {
        return path?.replace("\\", "/")
    }

    /**
     * 验证给定的路径是否匹配通配符模式
     *
     * glob:前缀告诉Java使用Glob语法解析通配符，**代表匹配任意深度的目录，*匹配零个或多个任意字符，而?匹配单个任意字符。
     *
     * @param path 要验证的路径字符串
     * @param wildcardPattern 通配符模式，支持glob模式匹配
     * @param fileNameSuffixes 可变参数，用于指定文件名后缀过滤器
     * @return 如果路径匹配模式则返回true，否则返回false
     */
    fun validatePath(path: String, wildcardPattern: String, vararg fileNameSuffixes: String): Boolean {
        return validatePath(Path(path), wildcardPattern, *fileNameSuffixes)
    }

    /**
     * 验证给定的[PackPath]是否匹配通配符模式
     *
     * glob: 前缀告诉Java使用Glob语法解析通配符，**代表匹配任意深度的目录，*匹配零个或多个任意字符，而?匹配单个任意字符。
     *
     * @param path 要验证的[PackPath]对象
     * @param wildcardPattern 通配符模式，支持glob模式匹配
     * @param fileNameSuffixes 可变参数，用于指定文件名后缀过滤器
     * @return 如果路径匹配模式则返回true，否则返回false
     */
    fun validatePath(path: PackPath, wildcardPattern: String, vararg fileNameSuffixes: String): Boolean {
        return validatePath(path.toPath, wildcardPattern, *fileNameSuffixes)
    }

    /**
     * 验证给定路径是否符合指定的通配符模式或过滤器条件
     *
     * glob:前缀告诉Java使用Glob语法解析通配符，**代表匹配任意深度的目录，*匹配零个或多个任意字符，而?匹配单个任意字符。
     *
     * @param path 要验证的路径对象
     * @param wildcardPattern 通配符模式字符串，如果不含":"则默认使用glob模式
     * @param fileNameSuffixes 可变参数，用于指定文件名后缀过滤器
     * @return 如果路径匹配任一过滤器或通配符模式则返回true，否则返回false
     */
    fun validatePath(path: Path, wildcardPattern: String, vararg fileNameSuffixes: String): Boolean {
        // 检查路径文件名是否以任一过滤器名称结尾
        val filter = fileNameSuffixes.any {
            path.name.endsWith(it, true)
        }
        if (filter) return false
        // 如果模式中不包含":"，则默认使用glob模式
        val pattern = if (wildcardPattern.indexOf(":") > 0)
            wildcardPattern else "glob:$wildcardPattern"

        val fileSystem = FileSystems.getDefault()
        val pathMatcher = fileSystem.getPathMatcher(pattern)
        val resultMatch = pathMatcher.matches(path)
        return resultMatch
    }


    /**
     * 根据空格分隔数据
     *
     * @param cmd
     * @return
     */
    fun cmdSplit(cmd: Any): Array<String> {
        // 按照空格分隔数据
        var cmd = cmd
//        if (cmd is LuaValue) {
//            cmd = cmd.tojstring()
//        }
        return (cmd as String).split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
    }

    fun packZip(savePath: String, zipName: String, updatePath: Array<String>, vararg itemPath: String) {
        val tempFile = PackPath(savePath, "asset")
        for (s in itemPath) {
            for (value in updatePath) {
                val resFile = PackPath(value, s)
                if (resFile.exists()) {
                    resFile.moveTo(tempFile.resolve(s), true)
                }
            }
        }
        if (tempFile.exists()) {
            ZipUtils.zip(PackPath(savePath, zipName).pathString, "", tempFile.pathString)
            tempFile.deleteRecursively()
        }
    }

}
